<?php
###########################################################################
#  Copyright (C) 2006-2007 Glyphix, Inc. <briz@glyphix.com>               #
#                                                                         #
#  Permission is hereby granted, free of charge, to any person obtaining  #
#  a copy of this software and associated documentation files (the        #
#  "Software"), to deal in the Software without restriction, including    #
#  without limitation the rights to use, copy, modify, merge, publish,    #
#  distribute, sublicense, and/or sell copies of the Software, and to     #
#  permit persons to whom the Software is furnished to do so, subject to  #
#  the following conditions:                                              #
#                                                                         #
#  The above copyright notice and this permission notice shall be         #
#  included in all copies or substantial portions of the Software.        #
#                                                                         #
#  Except as contained in this notice, the name(s) of the above           #
#  copyright holders shall not be used in advertising or otherwise to     #
#  promote the sale, use or other dealings in this Software without       #
#  prior written authorization.                                           #
#                                                                         #
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        #
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     #
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. #
#  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR      #
#  OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,  #
#  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR  #
#  OTHER DEALINGS IN THE SOFTWARE.                                        #
###########################################################################

/**
 * GXDB, a simple GXPage module for PDO.
 * It provides two methods -- set and get -- and handles all the additional work (such as binding variables) necessary when working with PDO.
 * @author		Brad Brizendine <briz@glyphix.com>
 * @link		http://www.glyphix.com/
 * @license		http://opensource.org/licenses/bsd-license.php BSD License
 * @version		1.0
 * @package		GXPage
 * @subpackage	Modules
 * @copyright	Copyright 2006 - 2007, Glyphix, Inc.
 * @uses		GXModule
 *
 * When GXPage instantiates it, GXPage passes it the XML config from System.xml.
 * If it receives a GXDOM object as its config, it will use the first connection it finds.
 * Once instantiated, you can create a new connection by passing it the name of a connection, as defined in System.xml.
 * GXModule caches each connection, and you can obtain access to them as shown in the example.
 *
 * @example get a connection from GXModule
 * // Assuming your class has defined GXPage as a class var:
 * // Database is the node name of the module definition in System.xml
 * // System is the node name of the connection definition in System.xml
 * $this->GXPage->Database->System
 *
 * @example opening a new connection
 * // Assuming there is a connection definition in System.xml called "Dev":
 * $devdb = new GXDB('Dev');
 *
 * @param mixed $config GXDOM config or name of a connection defined in the <Database> section of System.xml
 * @return mixed itself or false on failure
 */
class GXDB extends GXModule {

	// instructs GXDB::set to update instead of insert
	// use: GXDB::SQL_UPDATE
	const SQL_UPDATE	= 'SQL_UPDATE';
	const NO_FETCH		= 'NO_FETCH';
	const FETCH_ASSOC	= PDO::FETCH_ASSOC;
	const FETCH_OBJ		= PDO::FETCH_OBJ;
	const FETCH_BOTH	= PDO::FETCH_BOTH;

	// named connections
	private $drivers = array();

	public function __construct( $config = null ){
		parent::__construct();

		$name = null;
		switch(true){
			// if config is a string, treat it as a connection name
			case is_string($config):
				$name = $config;
				// get the named connection
				$xpath = '//Modules/Database/*[ name() = "' .$name .'" ]';
				$config = $this->SystemConfig->xpath($xpath,0);
				break;
			// if it's an xml object, treat it as the config from SystemConfig
			case get_class($config) == 'GXDOM':
				// get the first connection
				$xpath = '*[1]';
				$config = $config->exec->xpath($xpath,0);
				$name = $config->nodeName;
				break;
			// otherwise, we have an error
			default:
				trigger_error( 'Unable to parse config in GXDB constructor (' .gettype($config) .')', E_USER_ERROR );
				return false;
		}

		// instantiate the db subclass
		$type = strval($config->exec->xpath('Type',0)->nodeValue);
		$host = strval($config->exec->xpath('Host',0)->nodeValue);
		$database = strval($config->exec->xpath('Database',0)->nodeValue);

		// store the connect string
		$dsn = $type .':host=' .$host .';dbname=' .$database;
		// attempt to connect, and capture the error if we can't
		try{
			$db = new PDO( $dsn, strval($config->exec->xpath('User',0)->nodeValue), strval($config->exec->xpath('Password',0)->nodeValue) );
			$db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
			// set the timezone if we have one
			if( $tz = $this->GXPage->Run->Timezone ){
				if( $db->exec( 'SET TIME ZONE \'' .$tz .'\'' ) ){
					trigger_error( 'Set database timezone to ' .$tz, E_USER_WARNING );
				}
			}
		}catch( PDOException $e ){
			trigger_error( 'Unable to make a connection: ' .$e->getMessage() .'::' .print_r($e,true), E_USER_ERROR );
			return false;
		}

		trigger_error( 'GXDB connected to ' . $type .'::' .$database .' using config named ' .$name, E_USER_WARNING );

		// return the database-specific driver
		// don't use GXObject so we can have as many of them as we like
		$d = 'GXDB_' .$type;
		if( !($driver = new $d($db)) ){
			trigger_error( 'Unable to load driver ' .$d, E_USER_ERROR );
			return false;
		}

		// cache this driver
		$this->drivers[ $name ] = $driver;
		return $this;
	}

	/**
	 * __get
	 *
	 * Returns the GXDB_* connection by the name specified in SystemConfig.
	 * @param string $name the name of the connection defined in System.xml
	 * @return mixed the connection driver or false on failue
	 */
	function __get( $name = null ){
		if( isset($this->drivers[$name]) ){
			return $this->drivers[ $name ];
		}
		trigger_error( 'No connection by that name (' .$name .')', E_USER_WARNING );
		return false;
	}

}

/**
 * GXDB_driver, driver management for GXDB.
 * This class is only used by GXDB.
 * @author		Brad Brizendine <briz@glyphix.com>
 * @link		http://www.glyphix.com/
 * @license		http://opensource.org/licenses/bsd-license.php BSD License
 * @version		1.0
 * @package		GXPage
 * @subpackage	GXDB
 * @copyright	Copyright 2006 - 2007, Glyphix, Inc.
 *
 * @param object $PDO the PDO connection object as established by GXDB
 * @return mixed itself or false on failure
 */
class GXDB_driver {

	// PDO connection object, must be passed to this class' constructor
	private $PDO;

	// holds table definitions created in describe()
	private $Tables;

	/**
	 * __construct
	 *
	 * Sets the PDO connection.
	 */
	public function __construct( &$PDO = null ){
		if( !is_object($PDO) ){
			trigger_error( 'Missing database connection', E_USER_ERROR );
			return false;
		}
		$this->PDO = $PDO;
		return $this;
	}

	/**
	 * __get
	 *
	 * This is a stub for future functionality.
	 * @param string $name the name of the property
	 * @return mixed whatever the property is
	 */
	function __get( $name = null ){
		return $this->$name;
	}

	/**
	 * describe
	 *
	 * This function is extended by each database driver (see GXDB_psql for an example).
	 * When the database driver has completed its work, it calls its parent (this), providing the required params.
	 * @param string $table the name of the table
	 * @param array $def the SQL definition of the table as an array
	 * @return array the table definition or false on failure
	 */
	public function describe( $table = null, $def = null ){
		if( !$table || !$def ){
			trigger_error( 'Missing table or def', E_USER_ERROR );
			return false;
		}

		// cache the table definition
		$this->Tables[ $table ] = $def;
		return $def;
	}

	/**
	 * get
	 *
	 * Prepares the supplied sql, executes it and fetches its results.
	 *
	 * @param string $sql the query
	 * @param array $params associative array of parameters for the execute statement
	 * @param constant $fetching constant must be a valid PDO:: constant such as PDO::FETCH_OBJ
	 * @param boolean $fetch when true (default), performs a fetchAll; when false, returns the number of affected rows
	 * @return mixed whatever @fetching specifies or false on failure
	 */
	public function get(){
		// set up the args we'll use
		$sql = null;				// the sql string
		$params = array();			// associative array of parameters
		$fetching = PDO::FETCH_OBJ;	// pdo fetch style
		$fetch = true;				// whether to fetch or to just return rowCount
		$limit = null;				// number of records to return (if not set, get() returns fetchAll())
		$offset = null;				// record number to start the fetch

		// sense constants
		$args = func_get_args();
		$argmap = array(
			'sql',
			'params',
			'limit',
			'offset'
			);
		// loop thru all args, pulling the arg's name from the top of the argmap stack
		// if the arg equals one of this function's supported constants, don't touch the argmap
		foreach( $args as $a ){
			switch($a){
				case PDO::FETCH_ASSOC:
				case PDO::FETCH_OBJ:
				case PDO::FETCH_BOTH:
					$fetching = $a;
					continue;
					break;
				case GXDB::NO_FETCH:
					$fetch = false;
					break;
				default:
					$name = array_shift($argmap);
					${$name} = $a;
					break;
			}
		}

		// sanity check
		if( !$sql ){
			trigger_error( 'Missing sql', E_USER_ERROR );
			return false;
		}
		if( !$offset ){
			$offset = 0;
		}

		/*
		// determine if params is actually $fetching
		if( $params && !is_array($params) ){
			$fetching = $params;
			$fetch = $fetching;
			$params = array();
		}

		// default fetching
		if( !$fetching ){
			$fetching = PDO::FETCH_OBJ;
		}
		*/
		// add a colon as the first character of params if the first key doesn't start with one
		if( is_array($params) ){
			if( !is_numeric(strpos(key($params),':')) ){
				foreach( $params as $k => $v ){
					$params[ ':' .$k ] = $v;
					unset($params[$k]);
				}
			}
		}

		// init the query var
		$q = null;
		trigger_error( 'GXDB get operation: ' .$sql .' with ' .print_r($params,true), E_USER_WARNING );

		// if we have limit, start a transaction or pgsql won't create a proper scroll cursor
		if( is_numeric($limit) ){
			$this->PDO->beginTransaction();
		}

		// prepare the query
		try{
			// if we have limit, prepare a scrolling cursor
			$scroll = array();
			if( is_numeric($limit) ){
				$scroll = array( PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY );
			}
			$q = $this->PDO->prepare($sql,$scroll);
		}catch( PDOException $e ){
			trigger_error( 'PDO prepare error on ' .$sql .' with ' .print_r($params,true) .': ' .$e->getMessage(), E_USER_ERROR );
			return false;
		}

		// run it
		try{
			$q->execute($params);
		}catch( PDOException $e ){
			trigger_error( 'PDO error executing query: ' .$sql .' with ' .print_r($params,true) .': ' .$e->getMessage(), E_USER_ERROR );
			return false;
		}

		// this function is essentially exec, so only run fetch if we're asked to do so
		if( !$fetch ){
			// if we're not fetching, just return the number of affected rows
			return $q->rowCount();
		}

		// init the return var
		$r = null;

		// get the results using fetchAll, since there's a bug in pdo which won't allow scrolling cursors for pgsql
		try{
			$data = $q->fetchAll($fetching);
		}catch( PDOException $e ){
			trigger_error( 'PDO fetchAll() error on ' .$sql .' with ' .print_r($params,true) .': ' .$e->getMessage(), E_USER_ERROR );
			return false;
		}

		// if we have a limit, return only those rows necessary
		if( is_numeric($limit) ){
			$r = new stdClass();
			$r->rowCount = $q->rowCount();
			// loop thru the results starting at offset, offset plus limit times
			for( $i = $offset; $i < ($offset + $limit); $i++ ){
				if( $i >= $r->rowCount ){
					break;
				}
				$r->data[] = $data[$i];
			}
			$this->PDO->commit();
		}else{
			$r = $data;
		}
		unset($q);
		return $r;

		/*
		// get the results
		$r = null;
		if( !is_numeric($limit) ){
			// fetch all rows
		}else{
			// fetch specified rows
			// set the return class
			$r = new stdClass();
			// the total number of results
			$r->rowCount = $q->rowCount();
			// the rows as a numerically-indexed array
			$r->data = null;
			try{
				// loop thru the results starting at offset, offset plus limit times
				for( $i = $offset; $i < ($offset + $limit); $i++ ){
					if( $i >= $r->rowCount ){
						break;
					}
					$r->data[] = $q->fetch( $fetching, PDO::FETCH_ORI_ABS, $i );
				}
				// must close the cursor, or we'll have database problems
				$q->closeCursor();
			}catch( PDOException $e ){
				trigger_error( 'PDO fetch() error on ' .$sql .' with ' .print_r($params,true) .': ' .$e->getMessage(), E_USER_ERROR );
				return false;
			}
		}
		if( is_numeric($limit) ){
			$this->PDO->commit();
		}
		// free PDO
		unset($q);
		return $r;
		*/
	}

	public function run( $table = null, $params = null ){
		// return get, but tell it not to execute a fetch
		return $this->get( $table, $params, PDO::FETCH_OBJ, false );
	}

	/**
	 * set
	 *
	 * Performs an insert or update.
	 *
	 * @param string $table the name of the table
	 * @param array $params an associative array of name=>value pairs (not recursive) ... each key must match a column name exactly
	 * @param contant $operation instructs set to attempt an update if the value is GXPAGE::SQL_UPDATE
	 * @return mixed the last insert id or false on failure
	 */
	public function set( $table = null, $params = null, $operation = null ){
		if( !$table || !is_array($params) ){
			trigger_error( 'Missing table or params', E_USER_ERROR );
			return false;
		}

		// determine the magic quotes setting
		$stripEscapes = get_magic_quotes_gpc();

		// the table definition
		$def = null;
		// the unaffected names of the columns derived from params
		$cols['names'] = array();
		// the col names preceded with a colon
		$cols['params'] = array();
		// the col names preceded with a colon
		$pdovals = array();
		// param values formatted for updating
		$update = array();

		// get the table definition
		if( !($def = $this->describe($table)) ){
			trigger_error( 'Unable to describe ' .$table, E_USER_ERROR );
			return false;
		}

		// if we have a value for the first unique id and we're in pgsql, add a "returning" clause
		$returning = null;
		$returningstr = null;
		// generate list of unique columns
		$uniquecols = null;
		foreach( $def as $col => $conf ){
			if( $conf['unique'] ){
				$uniquecols[] = $col;
			}
		}
		if( $this->PDO->getAttribute(PDO::ATTR_DRIVER_NAME) == 'pgsql' ){
			if( count($uniquecols) ){
				$returning = implode(',',$uniquecols);
				$returningstr = ' returning ' .$returning;
			}
		}

		// generate the sql
		if( is_array($params) ){
			foreach( $params as $col => $val ){
				// make sure the database has the specified column
				if( !$def[ $col ] ){
					trigger_error( 'Table ' .$table .' has no column by that name (' .$col .')', E_USER_WARNING );
					continue;
				}

				// prefix colname for pdo
				$prm = ':' .$col;

				// an array of column names
				$cols['names'][] = $col;
				$cols['params'][] = $prm;

				// create the final param array
				$pdovals[$prm] = $stripEscapes ? stripslashes($val) : $val;

				// update format
				$update[$prm] = $col .'=' .$stripEscapes ? stripslashes($prm) : $prm;

				/*
				// if the param key has no colon, add it
				if( substr($col,0,1) != ':' ){
					$params[ ':' .$col ] = $val;
					unset($params[$col]);
				}
				*/
			}
		}

		// default to insert
		$sql = 'insert into ' .$table .' (' .implode(',',$cols['names']) .') values (' .implode(',',$cols['params']) .')' .$returningstr;

		// if we have an id, get the unique col(s) and override the sql
		if( $operation === GXDB::SQL_UPDATE ){
			// search for unique columns that are also in the provided params
			$updatecols = array_intersect($cols['names'],$uniquecols);
			if( count($updatecols) ){
				foreach( $updatecols as $col ){
					if( $cols['names'][$col] ){
						// pdo colname ... need to create a new param name, since this param is already sending data
						$prm = ':' .$col .'_uid';
						// build the WHERE query string
						$where[] = $col .'=' .$prm;
						// add the new param to the pdo params values
						$pdovals[$prm] = $pdovals[':' .$col];
						// unset this uid col from update
						unset($update[':' .$col]);
					}
				}
			}
			/*
			foreach( $cols['names'] as $col ){
				if( $def[$col]['unique'] ){

					// pdo colname ... need to create a new param name, since this param is already sending data
					$prm = ':' .$col .'_uid';

					// build the WHERE query string
					$where[] = $col .'=' .$prm;

					// add the new param to the pdo params values
					$pdovals[$prm] = $pdovals[':' .$col];
					// unset this uid col from update
					unset($update[':' .$col]);
				}
			}
			*/

			// check to see we have something in where
			if( !count($where) ){
				trigger_error( 'GXDB::SQL_UPDATE specified, but we have no unique columns to search by', E_USER_ERROR );
				return false;
			}

			// create the update sql
			$sql = 'update ' .$table .' set ' .implode(',',$update) .' where ' .implode(' and ',$where) .$returningstr;
		}

trigger_error( 'GXDB set operation: ' .$sql .' with ' .print_r($params,true), E_USER_WARNING );

		// if we're pgsql and we have returning, issue a fetch
		if( $returningstr ){
			return $this->get($sql,$pdovals);
		}

		// otherwise, prepare and execute
		try{
			$q = $this->PDO->prepare($sql);
		}catch( PDOException $e ){
			trigger_error( 'PDO prepare error on ' .$sql .' with ' .print_r($pdovals,true) .': ' .$e->getMessage(), E_USER_ERROR );
			return false;
		}
		// execute the statement
		try{
			$q->execute($pdovals);
		}catch( PDOException $e ){
			trigger_error( 'PDO execute error on ' .$sql .' with ' .print_r($pdovals,true) .': ' .$e->getMessage(), E_USER_ERROR );
			return false;
		}

		// get the last id inserted
		$lastid = $this->PDO->lastInsertId();
		// if we don't have one (the table might not have an auto-increment field), make it true
		if( !$lastid ){
			$lastid = true;
		}
		return $lastid;
	}

}

/**
 * GXDB_pgsql, GXDB driver for PostgreSQL.
 * This class is only used by GXDB.
 * @author		Brad Brizendine <briz@glyphix.com>
 * @link		http://www.glyphix.com/
 * @license		http://opensource.org/licenses/bsd-license.php BSD License
 * @version		1.0
 * @package		GXPage
 * @subpackage	GXDB
 * @copyright	Copyright 2006 - 2007, Glyphix, Inc.
 *
 * @param object $PDO the PDO connection object as established by GXDB
 * @return mixed itself or false on failure
 */
class GXDB_pgsql extends GXDB_driver {

	public function __construct( &$PDO = null ){
		parent::__construct($PDO);
		return $this;
	}

	/**
	 * describe
	 *
	 * Builds an array from the SQL definition of the table name.
	 * @param string $table the name of the table
	 * @param array $def included here to suppress E_STRICT warnings ... see GXDB::describe()
	 * @return array table definition or false on failure
	 */
	public function describe( $table = null, $def = null ){
		if( !$table ){
			trigger_error( 'Missing table', E_USER_ERROR );
			return false;
		}

		/**
		 * a way to determine the primary key column(s) in a table
		SELECT
		  attname::text
		FROM
		  pg_attribute
		JOIN
		  pg_class ON pg_attribute.attrelid = pg_class.oid
		JOIN
		  pg_namespace ON pg_namespace.oid = pg_class.relnamespace
		LEFT JOIN
		  pg_constraint ON conrelid = pg_class.oid AND pg_constraint.contype = 'p'
		WHERE
		  pg_namespace.nspname = 'MYNAMESPACE' AND
		  pg_class.relname = 'MYTABLE' AND
		  pg_attribute.attnum = ANY (pg_constraint.conkey)
		ORDER BY
		  pg_attribute.attnum;
		*/

		/**
		 * another query for getting table info
		SELECT
		  a.attnum,
		  a.attname AS field,
		  t.typname AS type,
		  a.attlen AS length,
		  a.atttypmod AS lengthvar,
		  a.attnotnull AS notnull
		FROM
		  pg_class c,
		  pg_attribute a,
		  pg_type t
		WHERE
		  c.relname = 'your_table_name'
		  AND a.attnum > 0
		  AND a.attrelid = c.oid
		  AND a.atttypid = t.oid
		  ORDER BY a.attnum
		*/

		// build the query to describe the supplied table
		$sql = 'select distinct '
			.'pg_attribute.attnum as fieldorder,'
			.'pg_attribute.attname as fieldname,'
			.'format_type(pg_attribute.atttypid, pg_attribute.atttypmod) as fieldtype,'
			.'case when pg_attribute.attnotnull = true then \'not null\' else \'null\' end as fieldnull,'
			.'('
				.'select '
					.'substring(pg_attrdef.adsrc for 128) '
				.'from '
					.'pg_catalog.pg_attrdef '
				.'where '
					.'pg_attrdef.adrelid = pg_attribute.attrelid '
					.'and pg_attrdef.adnum = pg_attribute.attnum '
					.'and pg_attribute.atthasdef'
			.') as fieldmodifiers '
		.'from '
			.'pg_attribute left join pg_attrdef on (pg_attribute.attrelid = pg_attrdef.adrelid ),'
			.'pg_type,'
			.'pg_class '
		.'where '
			.'lower(pg_class.relname) = \'' .strtolower($table) .'\' '
			.'and pg_attribute.attrelid = pg_class.oid '
			.'and pg_attribute.atttypid = pg_type.oid '
			.'and pg_attribute.attnum > 0 '
			.'and not pg_attribute.attisdropped '
		.'order by '
			.'fieldorder';

		// the initial table definition
		if( !($def = $this->get($sql,PDO::FETCH_ASSOC)) ){
			trigger_error( 'Unable to describe table ' .$table, E_USER_ERROR );
			return false;
		}

		// the gxpage table definition
		$tabledef = null;

		// build the table definition
		foreach( $def as $conf ){

			// split on paren, which leaves the field length as index 1
			$len = preg_split('/[()]/',$conf['fieldtype']);

			// check for nextval modifier; assume it's a unique id column
			$uid = false;
			if( is_numeric(strpos($conf['fieldmodifiers'],'nextval')) ){
				$uid = true;
			}

			// php type
			$phptype = null;
			switch(true){
				case is_numeric(strpos($conf['fieldtype'],'char')):
					$phptype = 'string';
					break;
				case is_numeric(strpos($conf['fieldtype'],'timestamp')):
					$phptype = 'timestamp';
					break;
				case is_numeric(strpos($conf['fieldtype'],'date')):
					$phptype = 'date';
					break;
				case is_numeric(strpos($conf['fieldtype'],'bit')):
					$phptype = 'string';
					break;
				default:
					$phptype = 'numeric';
			}

			$tabledef[ $conf['fieldname'] ] = array(
				'type'		=> $conf['fieldtype'],
				'length'	=> (isset($len[1])) ? $len[1] : null,
				'null'		=> $conf['fieldnull'],
				'modifiers'	=> $conf['fieldmodifiers'],
				'unique'	=> $uid,
				'phptype'	=> $phptype
				);
		}

		return parent::describe($table,$tabledef);
	}

}

/**
 * GXDB_mysql, GXDB driver for MySQL.
 * This class is only used by GXDB.
 * @author		Brad Brizendine <briz@glyphix.com>
 * @link		http://www.glyphix.com/
 * @license		http://opensource.org/licenses/bsd-license.php BSD License
 * @version		1.0
 * @package		GXPage
 * @subpackage	GXDB
 * @copyright	Copyright 2006 - 2007, Glyphix, Inc.
 *
 * @param object $PDO the PDO connection object as established by GXDB
 * @return mixed itself or false on failure
 */
class GXDB_mysql extends GXDB_driver {

	public function __construct( &$PDO = null ){
		parent::__construct($PDO);
		return $this;
	}

	/**
	 * describe
	 *
	 * Builds an array from the SQL definition of the table name.
	 * @param string $table the name of the table
	 * @param array $def included here to suppress E_STRICT warnings ... see GXDB::describe()
	 * @return array table definition or false on failure
	 */
	public function describe( $table = null, $def = null ){
		if( !$table ){
			trigger_error( 'Missing table', E_USER_ERROR );
			return false;
		}

		// get the definition
		if( !($def = $this->get('describe ' .$table)) ){
			trigger_error( 'Unable to describe ' .$table, E_USER_ERROR );
			return false;
		}

		/*
		foreach( $def as $col => $conf ){
			$Fields[$row[0]]=array(
				"type"=>ereg_replace("[[:digit:]]","",ereg_replace("[[:punct:]]","",$row[1])),
				"length"=>ereg_replace("[[:punct:]]","",ereg_replace("[[:alpha:]]","",$row[1])),
				"null"=>$row[2],
				"modifiers"=>$row[5]
				);
		}
		*/

		return $this->get('describe ' .$table);
	}

}

/**
 * GXDB_mssql, GXDB driver for Microsoft SQL.
 * This class is only used by GXDB.
 * @author		Brad Brizendine <briz@glyphix.com>
 * @link		http://www.glyphix.com/
 * @license		http://opensource.org/licenses/bsd-license.php BSD License
 * @version		1.0
 * @package		GXPage
 * @subpackage	GXDB
 * @copyright	Copyright 2006 - 2007, Glyphix, Inc.
 *
 * @param object $PDO the PDO connection object as established by GXDB
 * @return mixed itself or false on failure
 */
class GXDB_mssql extends GXDB_driver {

	public function __construct( &$PDO = null ){
		parent::__construct($PDO);
		return $this;
	}

	/**
	 * describe
	 *
	 * Builds an array from the SQL definition of the table name.
	 * @param string $table the name of the table
	 * @param array $def included here to suppress E_STRICT warnings ... see GXDB::describe()
	 * @return array table definition or false on failure
	 */
	public function describe( $table = null, $def = null ){
		trigger_error( 'GXDB_mssql is not implemented yet', E_USER_WARNING );
		return false;
	}

}

?>